const argv = require('yargs').argv
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const fs = require('fs')
const fse = require('fs-extra')
const exec = require('child_process').exec
const vueParser = require('vue-parser')
const packageImporter = require('node-sass-package-importer')
const sass = require('node-sass')
const postcss = require('postcss')
const pretty = require('pretty')
const stripFinalNewline = require('strip-final-newline')
const JSON5 = require('json5')
const cssmodules = require('postcss-icss-selectors')
const voca = require('voca')
const watch = require('node-watch')
const Cacheman = require('cacheman')
const projectconfig = require('./build.conf')
const html_minify = require('html-minifier').minify
const beautify_css = require('js-beautify').css
const buildWxmlPlugin = require('./build.plugin.wxml')

const csscache = new Cacheman('css')
const xmlcache = new Cacheman('xml')
const otscache = new Cacheman('ots')

// 检测两个文件之间变动
async function diffchange(key, data, cachestore) {
  let newval = data
  let cacval = await cachestore.get(key)
  let ret = 'init'
  // console.log(cacval);
  if (cacval) {
    let oldval = new Buffer(cacval.data)
    // console.log(newval);
    // console.log(oldval);
    if (oldval && oldval.equals && !oldval.equals(newval)) {
      ret = 'change'
    } else {
      ret = 'unchange'
    }
  }
  await cachestore.set(key, newval)
  return ret
}

const utils = {
  readFile: function (path) {
    try {
      return fs.readFileSync(path)
    } catch (e) {
      console.error(e)
    }
  },
  readJson(path) {
    if (!fs.existsSync(path)) {
      throw new Error('no json file')
    }
    return require(path)
  },
  readJSON5(path) {
    if (!fs.existsSync(path)) {
      throw new Error('no json file')
    }
    let data = fs.readFileSync(path, 'utf8')
    return JSON5.parse(data.toString())
  }
}

function parseWatchOptions(evt, filepath) {
  let paths = filepath.split(path.sep)
  let filename = paths[paths.length - 1]
  let folder = paths
    .filter(function (v, index) {
      if (index === paths.length - 1) {
        return false
      }
      return true
    })
    .join(path.sep)
  return {
    paths,
    filename,
    folder,
  }
}

const TMPFOLDERNAME = 'tmp'

function compile(filepath, destpath, options = {}) {
  let cwd = `npx babel ${filepath} --out-file ${destpath} && browserify  ${destpath} -s NameOfModule --outfile ${
    options.bundle
    } `
  if (options.sourcemap) {
    cwd = cwd + ' --source-maps'
  }
  return new Promise(function (resolve, reject) {
    exec(cwd, function (err, stdout, stderr) {
      if (err) throw err
      resolve()
    })
  })
}

function attributeToWxAttribute(str) {
  return str
    .split('"')
    .map(function (a, index) {
      if (index === 1) {
        return a
          .split(';')
          .map(function (v) {
            return `{{${v}}}`
          })
          .join(' ')
      }
      return a
    })
    .join('"')
}

function handleArrayDirective(exp) {
  let attr = ''
  let arr = exp.split(' in ')
  if (arr && arr.length > 1) {
    let left = arr[0].trim()
    let right = arr[1].trim()
    let s = left.split(',')
    attr = attr + `wx:for="{{${right}}}"`
    if (s.length === 1) {
      attr = attr + ` wx:for-item="${s[0].trim()}"`
    }

    if (s.length === 2) {
      let f = s
        .join()
        .split('(')[1]
        .split(')')[0]
        .split(',')
      attr = attr + ` wx:for-item="${f[0].trim()}"`
      attr = attr + ` wx:for-index="${f[1].trim()}"`
    }
  }

  if (arr && arr.length === 1) {
    attr = attr + `wx:for="{{${exp.trim()}}}"`
  }
  return attr
}

function traux(template, isWx = true) {
  let ret = template

  ret = ret.replace(/wx:for="/g, 'WXFOR="')
  ret = ret.replace(/wx:for-item="/g, 'WXFORITEM="')
  ret = ret.replace(/wx:key="/g, 'WXKEY="')
  ret = ret.replace(/wx:if="/g, 'WXIF="')
  ret = ret.replace(/wx:else/g, 'WXELSE')
  ret = ret.replace(/capture-catch:/g, 'CAPTURE')
  // https://regexr.com/459u7
  ret = ret.replace(/(bind:)(\w*=".*")/g, function (match, $1, $2) {
    return '___BIND___' + $2
  })

  // https://regexr.com/45007
  ret = ret.replace(
    /(<[^:>]*)(?::)([\w-]*=)([^>]*>)/g,
    function (match, $1, $2, $3) {
      let s = ' :' + $2 + $3


      // https://regexr.com/4500g
      s = s.replace(/([^\w]:)([\w-]+)(=")([^"]*)(")/g, function (
        match,
        $1_1,
        $1_2,
        $1_3,
        $1_4,
        $1_5,
      ) {
        if ($1_2 === 'key') {
          return $1_1.replace(':', 'wx:') + $1_2 + $1_3 + $1_4 + $1_5
        }
        return $1_1.replace(':', '') + $1_2 + $1_3 + `{{${$1_4}}}` + $1_5
      })

      return $1 + s
    },
  )

  // https://regexr.com/45er5
  ret = ret.replace(/(<)(?!\/)([^>]*)(>)/g, function (match, $1, $2, $3) {

    let s = $2

    //  https://regexr.com/44vun
    s = s.replace(
      /(?:@)([\w-]*)(=")([^"]*)(")/g,
      function (match, $1_1, $1_2, $1_3, $1_4) {
        let x = $1_1

        x = 'bind:' + x

        return x + $1_2 + $1_3 + $1_4
      },
    )

    return $1 + s + $3
  })


  ret = ret.replace(
    /(<[^>]*[\s]?[\w-]*\s*)(v-)([^=]*)(=")([^"]*)(")/g,
    function (match, $1, $2, $3, $4, $5, $6) {
      // 已 v- 开头的

      let directiveName = $3
      let value = $5

      if (directiveName === 'if') {
        return $1 + 'wx:if' + $4 + `{{${value}}}` + $6
      }

      if (directiveName === 'for') {
        // 处理for循环
        // console.log(handleArrayDirective(value))
        return $1 + handleArrayDirective(value)
      }

      return match
    },
  )

  // console.log('ret', ret)

  ret = ret.replace(
    /(<[\w-]+.*)(v-[\w:\s]+)([^">]*>)/g,
    function (match, $1, $2, $3) {
      let s = $2

      // TODO 测试 (<[\w-]+[^v]*)(v-[\w:\s]+)([^">]*>)
      s = s.replace(/(v-[\w]+=?"?)([^"]*)("?)/g, function (
        match,
        $2_1,
        $2_2,
        $2_3,
      ) {
        // 所有以v-开头的
        if ($2_1.indexOf('v-else') > -1) {
          return `wx:else`
        }

        return ''
      })
      return $1 + s + $3
    })

  ret = ret.replace(/WXFOR="/g, 'wx:for="')
  ret = ret.replace(/WXKEY="/g, 'wx:key="')
  ret = ret.replace(/WXIF="/g, 'wx:if="')
  ret = ret.replace(/WXELSE/g, 'wx:else')
  ret = ret.replace(/WXFORITEM/g, 'wx:for-item="')
  ret = ret.replace(/___BIND___/g, 'bind:')
  ret = ret.replace(/CAPTURE/g, 'capture-catch:')

  return pretty(ret)
}

let wxptemplate = function (tpl, {
  js,
  refs,
  fileName,
}) {
  return tpl.replace('@{js}', js)
    .replace(/@\{fileName\}/g, fileName)
    .replace(/@\{refs\}/g, JSON.stringify(refs ? refs : []))
}

let postcssConfig = require('./postcss.config')
let postcssPlugins = []
if (postcssConfig.plugins) {
  for (let pluginName in postcssConfig.plugins) {
    postcssPlugins.push(
      require(pluginName)(postcssConfig.plugins[pluginName]),
    )
  }
}

function getNoCommentsHtml(s) {
  var r = s
  let reg = /<!--[\s\S]*?-->/g
  r = r.replace(reg, '')
  return r
}

let refRegexp = /(<[^>]*)(ref=")([^"]*)(")([^>]*>)/g
function handleTemplateAndStyle(
  compiledStyle, filecontent, constants, destroot, compileFolderName, tmproot) {
  return new Promise(function (resolve, reject) {
    postcss(postcssPlugins)
      .process(compiledStyle)
      .then(result => {

        let refs = []

        let templateRegexp = /<template\s?>([\s\S]+?)<\/template>/g

        let myTemplateContents = ''

        filecontent.replace(templateRegexp, function ($match, $1) {
          myTemplateContents = $1
        })

        // console.log(myTemplateContents)

        myTemplateContents = myTemplateContents .replace(/^\/*(?!<)/, '')
          .trim()

        // console.log(myTemplateContents)

        let x = getNoCommentsHtml(myTemplateContents)

        x = x.replace(refRegexp, function (match, $1, $2, $3, $4, $5) {
          let s = 'ref-' + $3
          refs.push(s)
        })

        // https://regexr.com/45gav
        myTemplateContents = myTemplateContents.replace(refRegexp, function (match, $1, $2, $3, $4, $5) {
          let s = 'ref-' + $3
          return $1 + `id="` + s + $4 + $5
        })

        myTemplateContents = traux(myTemplateContents)

        myTemplateContents = buildWxmlPlugin(myTemplateContents, compileFolderName)

        // myTemplateContents = html_minify(myTemplateContents, {
        //   collapseWhitespace: true
        // })

        fse.outputFileSync(
          path.join(destroot, '/index.wxml'),
          myTemplateContents,
        )

        fse.outputFileSync(
          path.join(destroot, '/index.wxss'),
          beautify_css(result.css),
        )

        resolve({
          refs,
          myTemplateContents
        })
      })
  })
}

function handleVue(evt, filepath) {
  let {paths, filename, folder} = parseWatchOptions(evt, filepath)
  if (filepath.indexOf('___jb_') < 0) {
    let packagename = paths[paths.length - 2]
    if (filename.endsWith('.vue')) {
      let compileFolderName = paths[paths.length - 3]
      // console.log('handleVue packagename', packagename, compileFolderName)
      let comfolder = paths.slice(0, paths.length - 1).join(path.sep)
      let jsonPath = path.join(comfolder, 'index.json')
      let vuePath = path.join(comfolder, 'index.json')

      if (!fs.existsSync(vuePath)) {
        return
      }

      try {
        let componentJson = {}
        if (fs.existsSync(jsonPath)) {
          componentJson = utils.readJSON5(jsonPath)
        }

        let destfolder = folder.replace(projectconfig.config.workspaceroot, '')

        let tmpfolder = paths
          .slice(paths.length - 3, paths.length - 1)
          .join(path.sep)
        try {
          let filecontent = utils.readFile(filepath).toString()
          let destroot = path.join(projectconfig.config.destroot, destfolder)
          let tmproot = path.join(projectconfig.config.tmproot, destfolder)
          // console.log('tmproot', tmproot)

          if (filecontent) {
            console.log(chalk.cyan('build package ' + filepath))

            // filecontent = filecontent.replace('<script>', '<script lang="js">')
            //
            // let myScriptContents = vueParser.parse(filecontent, 'script', {
            //   lang: ['js'],
            // })
            let jsRegexp = /<script\s?>([\s\S]+?)<\/script>/g
            let myScriptContents = ''

            filecontent.replace(jsRegexp, function ($match, $1) {
              myScriptContents = $1
            })

            myScriptContents = myScriptContents.replace(
              /require\([\w-\s.\/'"]+\)/g,
              function (match) {
                return match.replace('require', 'WXREQUIRE')
              },
            )

            let tmppath = path.join(
              folder,
              `../../../${TMPFOLDERNAME}/${tmpfolder}/index.js`,
            )
            let tmpcompilepath = path.join(
              folder,
              `../../../${TMPFOLDERNAME}/${tmpfolder}/index.compile.js`,
            )
            let tmpbundlepath = path.join(
              folder,
              `../../../${TMPFOLDERNAME}/${tmpfolder}/index.bundle.js`,
            )
            let tmpminpath = path.join(
              folder,
              `../../../${TMPFOLDERNAME}/${tmpfolder}/index.min.js`,
            )
            let destscriptpath = path.join(
              projectconfig.config.destroot,
              `/${tmpfolder}/index.js`,
            )
            let mainjspath = path.join(
              projectconfig.config.destroot,
              `/${tmpfolder}/main.js`,
            )
            let destjsonpath = path.join(
              projectconfig.config.destroot,
              `/${tmpfolder}/index.json`,
            )

            // 确保目录存在
            fse.ensureDirSync(destroot)

            myScriptContents = myScriptContents
              .replace(/^\/\/\stslint:disable[\w\s\n\/]* tslint:enable/g, '')
              .trim()

            fse.outputFileSync(tmppath, myScriptContents)

            let filebasename = filename.replace(/.vue$/, '')

            // 先确保有文件
            fse.ensureFileSync(
              path.join(projectconfig.config.destroot, `/${tmpfolder}/index.js`),
            )

            let css = {}
            let ret = {}
            ret.$is = paths[paths.length - 2]

            let constants = {
              '%=IS%': ret.$is,
            }

            compile(tmppath, tmpcompilepath, {
              bundle: tmpbundlepath,
            })
              .then(function () {
                // fse.copySync(tmpcompilepath, destscriptpath);

                let compilejs = fs.readFileSync(tmpbundlepath).toString()

                compilejs = compilejs.replace(
                  /WXREQUIRE\([\w-\s.\/'"]+\)/g,
                  function (match) {
                    return match.replace('WXREQUIRE', 'require')
                  },
                )

                const matchStyleRegex = /<style\s?lang="(\w+)">([^<]*)?<\/style>/g

                let compiledStyle = ''
                let compileTasks = []
                let myStyleContents = stripFinalNewline(filecontent).replace(/\n/g, '').trim()

                let sortedIndex = 0
                let compiledResult = []

                const less = require('less')

                myStyleContents.replace(matchStyleRegex, function (match, $1, $2) {
                  if ($2 && $1) {
                    let compileStyleMethod = $1

                    sortedIndex = sortedIndex + 1
                    let _sortedIndex = sortedIndex

                    compileTasks.push(new Promise(function (resolve, reject) {

                      if (compileStyleMethod === 'scss') {
                        let res = sass.renderSync({
                          data: $2,
                          importer: packageImporter({}),
                          includePaths: [folder, projectconfig.config.workspaceroot],
                          functions: {},
                        })
                        compiledResult.push({
                          sorted: _sortedIndex,
                          type: compileStyleMethod,
                          res,
                        })

                        resolve()
                      } else if (compileStyleMethod === 'less') {
                        less.render($2, {
                          paths: [folder],
                        }).then(res => {
                          compiledResult.push({
                            sorted: _sortedIndex,
                            type: compileStyleMethod,
                            res,
                          })
                          resolve()
                        }).catch(e => {
                          console.error(e)
                          reject(e)
                        })
                      } else {
                        compiledResult.push({
                          sorted: _sortedIndex,
                          type: compileStyleMethod,
                          res: {
                            css: `${compiledStyle}
${$2}`,
                          },
                        })

                        resolve()
                      }

                    }))
                  }
                })

                Promise.all(compileTasks).then(function () {
                  compiledResult = compiledResult.sort((a, b) => a.sorted - b.sorted)
                  compiledResult.forEach(v => {
                    compiledStyle = compiledStyle + v.res.css
                  })


                  handleTemplateAndStyle(
                    compiledStyle,
                    filecontent,
                    constants,
                    destroot,
                    compileFolderName,
                    tmproot)
                    .then(compiledRes => {
                      let wxp, temlpateModule;

                      console.log('compiledRes', compiledRes.refs)

                      if (componentJson.component) {
                        temlpateModule = require(path.join(
                          __dirname,
                          './template/wxc.js',
                        ))

                        wxp = wxptemplate(
                          temlpateModule.tpl,
                          {
                            js: `require('./main.js')`,
                            fileName: paths[paths.length - 2],
                            refs: compiledRes.refs
                          },
                        )

                      } else {
                        temlpateModule = require(path.join(
                          __dirname,
                          './template/wxp.js',
                        ))

                        wxp = wxptemplate(
                          temlpateModule.tpl,
                          {
                            js: `require('./main.js')`,
                            fileName: paths[paths.length - 2],
                            refs: compiledRes.refs
                          },
                        )
                      }

                      fse.outputFileSync(destscriptpath, wxp)
                      fse.outputFileSync(
                        mainjspath,
                        `${temlpateModule.bef}${compilejs}`,
                      )
                      fse.outputFileSync(destjsonpath, JSON.stringify(componentJson))
                    })
                    .catch(e => console.error(e))

                }).catch(error => {
                  console.log(error)
                })

              })
              .catch(e => {
                console.error(e)
                throw e
              })
          }
        } catch (e) {
          console.error(e)
          throw e
        }
        // end readJson
      } catch (e) {
        console.error(`readJson failed`, e)
        throw e
      }
    }
  }
}

function compileVue(folderName) {
  let dir = path.join(`${projectconfig.config.workspaceroot}`, folderName)
  let componentsPaths = fs.readdirSync(dir)
  // console.log(componentsPaths)
  for (let componentPath of componentsPaths) {
    let _path = path.join(dir, componentPath)
    let fileContentState = fs.lstatSync(_path)
    if (
      fileContentState.isDirectory()
    ) {
      handleVue({}, path.join(_path, 'index.vue'), folderName)
    } else {
      handleVue({}, _path, folderName)
    }
  }
}

function compileUtils(fileName = 'index', utilsFolder = 'utils/compile') {
  let srcfolder = path.join(projectconfig.config.workspaceroot, utilsFolder)
  let destFolder = path.join(projectconfig.config.destroot, utilsFolder)
  let tmpFolder = path.join(projectconfig.config.tmproot, utilsFolder)

  fse.ensureDirSync(destFolder)
  fse.ensureDirSync(tmpFolder)

  let fileContent = utils.readFile(path.join(srcfolder, fileName + '.js')).toString()
  fileContent = fileContent.replace(/require\([\w-\s.\/'"]+\)/g, function (
    match,
  ) {
    return match.replace('require', 'WXREQUIRE')
  })

  fse.outputFileSync(path.join(tmpFolder, `${fileName}.js`), fileContent)

  compile(
    path.join(tmpFolder, `${fileName}.js`),
    path.join(tmpFolder, `${fileName}.compile.js`),
    {
      bundle: path.join(tmpFolder, `${fileName}.bundle.js`),
    },
  ).then(function () {
    let compilejs = utils
      .readFile(path.join(tmpFolder, `${fileName}.bundle.js`))
      .toString()
    compilejs = compilejs.replace(/WXREQUIRE\([\w-\s.\/'"]+\)/g, function (
      match,
    ) {
      return match.replace('WXREQUIRE', 'require')
    })

    compilejs = `
    const regeneratorRuntime = require('../../static/runtime.js');

    ${compilejs}
    `

    fse.outputFileSync(path.join(destFolder, `${fileName}.js`), compilejs)
  })
}


function getFileNameWidthOutExtension(fileName) {
  return fileName.split('.').slice(0, -1).join('.')
}

function readDirSync(path, cb) {
  let pa = fs.readdirSync(path)
  pa.forEach(function (ele, index) {
    let info = fs.statSync(path + '/' + ele)
    if (info.isDirectory()) {
      // console.log("dir: "+ele)
      readDirSync(path + '/' + ele, cb)
    } else {
      // console.log("file: " + ele)
      cb(ele, info)
    }
  })
}

const spinner = ora('building for production... ')
spinner.start()

if (argv.compile_components) {
  compileVue('components')
}

if (argv.compile_pages) {
  compileVue('pages')
}

if (argv.folder) {
  if (argv.folder.indexOf(',') > -1) {
    let _f = argv.folder.split(',')
    _f.forEach(i => {
      compileVue(i)
    })
  } else {
    compileVue(argv.folder)
  }
}


// 编译utils
readDirSync(projectconfig.config.utilsproot[0], function (file, fileInfo) {
  let fileNameWithOutExtension = getFileNameWidthOutExtension(file)
  compileUtils(fileNameWithOutExtension)
})

if (argv.watch) {
  watch(projectconfig.config.wxproot, {recursive: true}, handleVue)

  watch(projectconfig.config.utilsproot, {recursive: true}, function (evt, filepath) {
    let {paths, filename, folder} = parseWatchOptions(evt, filepath)
    if (filepath.indexOf('___jb_') < 0) {
      if (filename.endsWith('.js')) {
        if (!filename.startsWith('_')) {
          console.info(`${filename} changed  compile `)
          let fileNameWithOutExtension = getFileNameWidthOutExtension(filename)
          compileUtils(fileNameWithOutExtension)
        }
      }
    }
  })
} else {
  spinner.stop()
}

